package hydrap2p.download.strategy;

import java.util.Properties;

import hydrap2p.download.Download;
import hydrap2p.download.DownloaderImpl;
import hydrap2p.download.ProtocolAdapter;
import hydrap2p.download.Source;
import hydrap2p.helper.ServiceConnector;
import hydrap2p.library.Range;
import hydrap2p.logging.Level;
import hydrap2p.logging.Logging;

import org.apache.log4j.PropertyConfigurator;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

/**
 * The Class BasicStrategy.
 */
public class BasicStrategy implements DownloadStrategy {
	// Init logger
	static {
		//Configure log4j
		Properties log4jProps = new Properties();
		log4jProps.setProperty("log4j.rootLogger", "TRACE, console");
		log4jProps.setProperty("log4j.appender.console", "org.apache.log4j.ConsoleAppender");
		log4jProps.setProperty("log4j.appender.console.layout", "org.apache.log4j.PatternLayout");
		log4jProps.setProperty("log4j.appender.console.layout.ConversionPattern", "%-4r [%t] %-5p %c %x - %m%n");

		PropertyConfigurator.configure(log4jProps);
	}
	
	// Class string name, doubling as logging marker
	private static final Marker TAG = MarkerFactory.getDetachedMarker("BasicStrategy");

	// Console logger - used before connections established
	private static final Logger consoleLogger = LoggerFactory.getLogger(TAG.getName());
	
	private int CHUNK_SIZE;
	
	/**
	 * Instantiates a new basic strategy.
	 * 
	 * @param CHUNK_SIZE the cHUN k_ size
	 */
	public BasicStrategy(int CHUNK_SIZE) {
		this.CHUNK_SIZE = CHUNK_SIZE;
	}
	
	/* (non-Javadoc)
	 * @see hydrap2p.download.strategy.DownloadStrategy#downloadNextChunk(hydrap2p.download.DownloaderImpl, long, hydrap2p.download.Source)
	 */
	@Override
	public void downloadNextChunk(DownloaderImpl downloader, final long uid, final Source source) throws TException {
		if(source == null) {
			downloader.erroredDownloading(uid, source, null, true);
			return;
		}
		
		Download d = downloader.getImplDownloadWithUid(uid);
		if(d == null) {
			log(Level.MISHAP, "Null download!  Exiting.");
			return;
		}

		log(Level.DEBUG, "Download info: ");
		log(Level.DEBUG, "Available size: " + d.getAvailable().size());
		
		log(Level.DEBUG, "Starting download of next chunk from source: "+ source.ip + ":" + source.port + " - " + source.protocol);

		synchronized(d)
		{
			if(d.getAvailable().size() > 0) {
				//Get first available range
				Range range = d.getAvailable().get(0);
				if(range == null) {
					log(Level.MISHAP, "Source has no more data: " + source.ip + ":" + source.port + " - " + source.protocol);
					d.getAvailable().remove(0);
					return;
				} else
					log(Level.DEBUG, "Got chunk with size: " + range.len / 1024 + " kb");
				
				//If bigger than chunk size, split it
				if(range.getLen() > CHUNK_SIZE) {
					Range first = new Range(range.start, CHUNK_SIZE);
					Range second = new Range(range.start + CHUNK_SIZE, range.len - CHUNK_SIZE);
					
					log(Level.DEBUG, "Splitting chunk: ");
					log(Level.DEBUG, "First: " + first.len / 1024 + " kb");
					log(Level.DEBUG, "Second: " + second.len / 1024 + " kb");
					
					//Remove the range we're downloading from available
					d.available.remove(0);
					d.addAvailableRange(second);
					
					range = first;
				} else
					d.available.remove(0);
				
				final Range finalRange = range;
				
				log(Level.DEBUG, "Source protocol: " + source.protocol);
				
				final ProtocolAdapter p = downloader.getProtocolByType(source.protocol);
				
				if(p != null) {
					log(Level.DEBUG, "Protocol Adapter: " + p.getLocation().getAddress() + ":" + p.getLocation().getPort() + " - " + p.getType());
					
					Thread t = new Thread(new Runnable() {
						@Override
						public void run() {
							try {
								p.download(uid, source, finalRange);
							} catch(TException e) {
								log(Level.ERROR, "TException thrown by download call!");
								e.printStackTrace();
							}
						}
					});
					
					t.start();
				}
				else {
					log(Level.ERROR, "Failed to contact protocol: " + source.protocol);
				}
			} else {
				log(Level.MISHAP, "Failed to download chunk!  No more data available?");
				//TODO Decide how to resume later - on add source?
			}
		}
	}
	
	/**
	 * Log.
	 * 
	 * @param severity the severity
	 * @param logMessage the log message
	 */
	public void log(Level severity, String logMessage) {
		Logging.Client logging = ServiceConnector.getLogger();
		
		//Log to Logging module if possible, and fall back to console.
		try {
			logging.log(severity, TAG.toString(), logMessage);
		} catch(TException e) {
			logToSystemError(severity, logMessage);
		} catch(NullPointerException e) {
			logToSystemError(severity, logMessage);
		}
	}
	
	/**
	 * Log to system error.
	 * 
	 * @param severity the severity
	 * @param logMessage the log message
	 */
	private void logToSystemError(Level severity, String logMessage) {
		//Log to system error.  If they can't be arsed to use a Logging constant as severity, the message gets logged at trace level
		switch(severity) {
		case VERBOSE:
			consoleLogger.trace(TAG, logMessage);
			break;
		case DEBUG:
			consoleLogger.debug(TAG, logMessage);
			break;
		case INFO:
			consoleLogger.info(TAG, logMessage);
			break;
		case MISHAP:
			consoleLogger.warn(TAG, logMessage);
			break;
		case ERROR:
			consoleLogger.error(TAG, logMessage);
			break;
		default:
			consoleLogger.trace(TAG, logMessage);
		}
	}
}
